import {arrayFindIndex} from 'element-ui/src/utils/util';
import {getCell, getColumnByCell, getRowIdentity} from './util';
import {getStyle, hasClass, removeClass, addClass} from 'element-ui/src/utils/dom';
import ElCheckbox from 'element-ui/packages/checkbox';
import ElTooltip from 'element-ui/packages/tooltip';
import debounce from 'throttle-debounce/debounce';
import LayoutObserver from './layout-observer';
import {mapStates} from './store/helper';

export default {
    name: 'ElTableBody',

    mixins: [LayoutObserver],

    components: {
        ElCheckbox,
        ElTooltip
    },

    props: {
        store: {
            required: true
        },
        stripe: Boolean,
        context: {},
        rowClassName: [String, Function],
        rowStyle: [Object, Function],
        fixed: String,
        highlight: Boolean
    },

    render(h) {
        const data = this.data || [];
        return (
            < table
    class
        = "el-table__body"
        cellspacing = "0"
        cellpadding = "0"
        border = "0" >
            < colgroup >
            {
                this.columns.map(column => < col
        name = {column.id
    }
        key = {column.id
    }
        />)
    }
    <
        /colgroup>
        < tbody >
        {
            data.reduce((acc, row) => {
            return acc.concat(this.wrappedRowRender(row, acc.length));
        }, [])
    }
    <
        el - tooltip
        effect = {this.table.tooltipEffect
    }
        placement = "top"
        ref = "tooltip"
        content = {this.tooltipContent
    }><
        /el-tooltip>
        < /tbody>
        < /table>
    )
        ;
    },

    computed: {
        table() {
            return this.$parent;
        },

        ...mapStates({
            data: 'data',
            columns: 'columns',
            treeIndent: 'indent',
            leftFixedLeafCount: 'fixedLeafColumnsLength',
            rightFixedLeafCount: 'rightFixedLeafColumnsLength',
            columnsCount: states => states.columns.length,
            leftFixedCount: states => states.fixedColumns.length,
            rightFixedCount: states => states.rightFixedColumns.length,
            hasExpandColumn: states => states.columns.some(({type}) => type === 'expand')
        }),

        firstDefaultColumnIndex() {
            return arrayFindIndex(this.columns, ({type}) => type === 'default');
        }
    },

    watch: {
        // don't trigger getter of currentRow in getCellClass. see https://jsfiddle.net/oe2b4hqt/
        // update DOM manually. see https://github.com/ElemeFE/element/pull/13954/files#diff-9b450c00d0a9dec0ffad5a3176972e40
        'store.states.hoverRow'(newVal, oldVal) {
            if (!this.store.states.isComplex || this.$isServer) return;
            let raf = window.requestAnimationFrame;
            if (!raf) {
                raf = (fn) => setTimeout(fn, 16);
            }
            raf(() => {
                const rows = this.$el.querySelectorAll('.el-table__row');
                const oldRow = rows[oldVal];
                const newRow = rows[newVal];
                if (oldRow) {
                    removeClass(oldRow, 'hover-row');
                }
                if (newRow) {
                    addClass(newRow, 'hover-row');
                }
            });
        }
    },

    data() {
        return {
            tooltipContent: ''
        };
    },

    created() {
        this.activateTooltip = debounce(50, tooltip => tooltip.handleShowPopper());
    },

    methods: {
        getKeyOfRow(row, index) {
            const rowKey = this.table.rowKey;
            if (rowKey) {
                return getRowIdentity(row, rowKey);
            }
            return index;
        },

        isColumnHidden(index) {
            if (this.fixed === true || this.fixed === 'left') {
                return index >= this.leftFixedLeafCount;
            } else if (this.fixed === 'right') {
                return index < this.columnsCount - this.rightFixedLeafCount;
            } else {
                return (index < this.leftFixedLeafCount) || (index >= this.columnsCount - this.rightFixedLeafCount);
            }
        },

        getSpan(row, column, rowIndex, columnIndex) {
            let rowspan = 1;
            let colspan = 1;
            const fn = this.table.spanMethod;
            if (typeof fn === 'function') {
                const result = fn({
                    row,
                    column,
                    rowIndex,
                    columnIndex
                });
                if (Array.isArray(result)) {
                    rowspan = result[0];
                    colspan = result[1];
                } else if (typeof result === 'object') {
                    rowspan = result.rowspan;
                    colspan = result.colspan;
                }
            }
            return {rowspan, colspan};
        },

        getRowStyle(row, rowIndex) {
            const rowStyle = this.table.rowStyle;
            if (typeof rowStyle === 'function') {
                return rowStyle.call(null, {
                    row,
                    rowIndex
                });
            }
            return rowStyle || null;
        },

        getRowClass(row, rowIndex) {
            const classes = ['el-table__row'];
            if (this.table.highlightCurrentRow && row === this.store.states.currentRow) {
                classes.push('current-row');
            }

            if (this.stripe && rowIndex % 2 === 1) {
                classes.push('el-table__row--striped');
            }
            const rowClassName = this.table.rowClassName;
            if (typeof rowClassName === 'string') {
                classes.push(rowClassName);
            } else if (typeof rowClassName === 'function') {
                classes.push(rowClassName.call(null, {
                    row,
                    rowIndex
                }));
            }

            if (this.store.states.expandRows.indexOf(row) > -1) {
                classes.push('expanded');
            }

            return classes;
        },

        getCellStyle(rowIndex, columnIndex, row, column) {
            const cellStyle = this.table.cellStyle;
            if (typeof cellStyle === 'function') {
                return cellStyle.call(null, {
                    rowIndex,
                    columnIndex,
                    row,
                    column
                });
            }
            return cellStyle;
        },

        getCellClass(rowIndex, columnIndex, row, column) {
            const classes = [column.id, column.align, column.className];

            if (this.isColumnHidden(columnIndex)) {
                classes.push('is-hidden');
            }

            const cellClassName = this.table.cellClassName;
            if (typeof cellClassName === 'string') {
                classes.push(cellClassName);
            } else if (typeof cellClassName === 'function') {
                classes.push(cellClassName.call(null, {
                    rowIndex,
                    columnIndex,
                    row,
                    column
                }));
            }

            return classes.join(' ');
        },

        getColspanRealWidth(columns, colspan, index) {
            if (colspan < 1) {
                return columns[index].realWidth;
            }
            const widthArr = columns.map(({realWidth}) => realWidth).slice(index, index + colspan);
            return widthArr.reduce((acc, width) => acc + width, -1);
        },

        handleCellMouseEnter(event, row) {
            const table = this.table;
            const cell = getCell(event);

            if (cell) {
                const column = getColumnByCell(table, cell);
                const hoverState = table.hoverState = {cell, column, row};
                table.$emit('cell-mouse-enter', hoverState.row, hoverState.column, hoverState.cell, event);
            }

            // 判断是否text-overflow, 如果是就显示tooltip
            const cellChild = event.target.querySelector('.cell');
            if (!(hasClass(cellChild, 'el-tooltip') && cellChild.childNodes.length)) {
                return;
            }
            // use range width instead of scrollWidth to determine whether the text is overflowing
            // to address a potential FireFox bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1074543#c3
            const range = document.createRange();
            range.setStart(cellChild, 0);
            range.setEnd(cellChild, cellChild.childNodes.length);
            const rangeWidth = range.getBoundingClientRect().width;
            const padding = (parseInt(getStyle(cellChild, 'paddingLeft'), 10) || 0) +
                (parseInt(getStyle(cellChild, 'paddingRight'), 10) || 0);
            if ((rangeWidth + padding > cellChild.offsetWidth || cellChild.scrollWidth > cellChild.offsetWidth) && this.$refs.tooltip) {
                const tooltip = this.$refs.tooltip;
                // TODO 会引起整个 Table 的重新渲染，需要优化
                this.tooltipContent = cell.innerText || cell.textContent;
                tooltip.referenceElm = cell;
                tooltip.$refs.popper && (tooltip.$refs.popper.style.display = 'none');
                tooltip.doDestroy();
                tooltip.setExpectedState(true);
                this.activateTooltip(tooltip);
            }
        },

        handleCellMouseLeave(event) {
            const tooltip = this.$refs.tooltip;
            if (tooltip) {
                tooltip.setExpectedState(false);
                tooltip.handleClosePopper();
            }
            const cell = getCell(event);
            if (!cell) return;

            const oldHoverState = this.table.hoverState || {};
            this.table.$emit('cell-mouse-leave', oldHoverState.row, oldHoverState.column, oldHoverState.cell, event);
        },

        handleMouseEnter: debounce(30, function (index) {
            this.store.commit('setHoverRow', index);
        }),

        handleMouseLeave: debounce(30, function () {
            this.store.commit('setHoverRow', null);
        }),

        handleContextMenu(event, row) {
            this.handleEvent(event, row, 'contextmenu');
        },

        handleDoubleClick(event, row) {
            this.handleEvent(event, row, 'dblclick');
        },

        handleClick(event, row) {
            this.store.commit('setCurrentRow', row);
            this.handleEvent(event, row, 'click');
        },

        handleEvent(event, row, name) {
            const table = this.table;
            const cell = getCell(event);
            let column;
            if (cell) {
                column = getColumnByCell(table, cell);
                if (column) {
                    table.$emit(`cell-${name}`, row, column, cell, event);
                }
            }
            table.$emit(`row-${name}`, row, column, event);
        },

        rowRender(row, $index, treeRowData) {
            const {treeIndent, columns, firstDefaultColumnIndex} = this;
            const columnsHidden = columns.map((column, index) => this.isColumnHidden(index));
            const rowClasses = this.getRowClass(row, $index);
            let display = true;
            if (treeRowData) {
                rowClasses.push('el-table__row--level-' + treeRowData.level);
                display = treeRowData.display;
            }
            // 指令 v-show 会覆盖 row-style 中 display
            // 使用 :style 代替 v-show https://github.com/ElemeFE/element/issues/16995
            let displayStyle = display ? null : {
                display: 'none'
            };
            return ( < tr
            style = {[displayStyle, this.getRowStyle(row, $index)
        ]
        }
        class
            = {rowClasses}
            key = {this.getKeyOfRow(row, $index)
        }
            on - dblclick = {($event)
        =>
            this.handleDoubleClick($event, row)
        }
            on - click = {($event)
        =>
            this.handleClick($event, row)
        }
            on - contextmenu = {($event)
        =>
            this.handleContextMenu($event, row)
        }
            on - mouseenter = {_
        =>
            this.handleMouseEnter($index)
        }
            on - mouseleave = {this.handleMouseLeave
        }>
            {
                columns.map((column, cellIndex) => {
                    const {rowspan, colspan} = this.getSpan(row, column, $index, cellIndex);
                    if (!rowspan || !colspan) {
                        return null;
                    }
                    const columnData = {...column};
                    columnData.realWidth = this.getColspanRealWidth(columns, colspan, cellIndex);
                    const data = {
                        store: this.store,
                        _self: this.context || this.table.$vnode.context,
                        column: columnData,
                        row,
                        $index
                    };
                    if (cellIndex === firstDefaultColumnIndex && treeRowData) {
                        data.treeNode = {
                            indent: treeRowData.level * treeIndent,
                            level: treeRowData.level
                        };
                        if (typeof treeRowData.expanded === 'boolean') {
                            data.treeNode.expanded = treeRowData.expanded;
                            // 表明是懒加载
                            if ('loading' in treeRowData) {
                                data.treeNode.loading = treeRowData.loading;
                            }
                            if ('noLazyChildren' in treeRowData) {
                                data.treeNode.noLazyChildren = treeRowData.noLazyChildren;
                            }
                        }
                    }
                    return (
                        < td
                    style = {this.getCellStyle($index, cellIndex, row, column)
                }
                class
                    = {this.getCellClass($index, cellIndex, row, column)
                }
                    rowspan = {rowspan}
                    colspan = {colspan}
                    on - mouseenter = {($event)
                =>
                    this.handleCellMouseEnter($event, row)
                }
                    on - mouseleave = {this.handleCellMouseLeave
                }>
                    {
                        column.renderCell.call(
                            this._renderProxy,
                            this.$createElement,
                            data,
                            columnsHidden[cellIndex]
                        )
                    }
                <
                    /td>
                )
                    ;
                })
            }
        <
            /tr>);
        },

        wrappedRowRender(row, $index) {
            const store = this.store;
            const {isRowExpanded, assertRowKey} = store;
            const {treeData, lazyTreeNodeMap, childrenColumnName, rowKey} = store.states;
            if (this.hasExpandColumn && isRowExpanded(row)) {
                const renderExpanded = this.table.renderExpanded;
                const tr = this.rowRender(row, $index);
                if (!renderExpanded) {
                    console.error('[Element Error]renderExpanded is required.');
                    return tr;
                }
                // 使用二维数组，避免修改 $index
                return [[
                    tr,
                < tr key = {'expanded-row__' +tr.key} >
                    < td colspan = {this.columnsCount
            } class
                = "el-table__expanded-cell" >
                    {renderExpanded(this.$createElement, {row, $index, store: this.store}
            )
            }
            <
                /td>
                < /tr>]];
            } else if (Object.keys(treeData).length) {
                assertRowKey();
                // TreeTable 时，rowKey 必须由用户设定，不使用 getKeyOfRow 计算
                // 在调用 rowRender 函数时，仍然会计算 rowKey，不太好的操作
                const key = getRowIdentity(row, rowKey);
                let cur = treeData[key];
                let treeRowData = null;
                if (cur) {
                    treeRowData = {
                        expanded: cur.expanded,
                        level: cur.level,
                        display: true
                    };
                    if (typeof cur.lazy === 'boolean') {
                        if (typeof cur.loaded === 'boolean' && cur.loaded) {
                            treeRowData.noLazyChildren = !(cur.children && cur.children.length);
                        }
                        treeRowData.loading = cur.loading;
                    }
                }
                const tmp = [this.rowRender(row, $index, treeRowData)];
                // 渲染嵌套数据
                if (cur) {
                    // currentRow 记录的是 index，所以还需主动增加 TreeTable 的 index
                    let i = 0;
                    const traverse = (children, parent) => {
                        if (!(children && children.length && parent)) return;
                        children.forEach(node => {
                            // 父节点的 display 状态影响子节点的显示状态
                            const innerTreeRowData = {
                                display: parent.display && parent.expanded,
                                level: parent.level + 1
                            };
                            const childKey = getRowIdentity(node, rowKey);
                            if (childKey === undefined || childKey === null) {
                                throw new Error('for nested data item, row-key is required.');
                            }
                            cur = {...treeData[childKey]};
                            // 对于当前节点，分成有无子节点两种情况。
                            // 如果包含子节点的，设置 expanded 属性。
                            // 对于它子节点的 display 属性由它本身的 expanded 与 display 共同决定。
                            if (cur) {
                                innerTreeRowData.expanded = cur.expanded;
                                // 懒加载的某些节点，level 未知
                                cur.level = cur.level || innerTreeRowData.level;
                                cur.display = !!(cur.expanded && innerTreeRowData.display);
                                if (typeof cur.lazy === 'boolean') {
                                    if (typeof cur.loaded === 'boolean' && cur.loaded) {
                                        innerTreeRowData.noLazyChildren = !(cur.children && cur.children.length);
                                    }
                                    innerTreeRowData.loading = cur.loading;
                                }
                            }
                            i++;
                            tmp.push(this.rowRender(node, $index + i, innerTreeRowData));
                            if (cur) {
                                const nodes = lazyTreeNodeMap[childKey] || node[childrenColumnName];
                                traverse(nodes, cur);
                            }
                        });
                    };
                    // 对于 root 节点，display 一定为 true
                    cur.display = true;
                    const nodes = lazyTreeNodeMap[key] || row[childrenColumnName];
                    traverse(nodes, cur);
                }
                return tmp;
            } else {
                return this.rowRender(row, $index);
            }
        }
    }
};
